昨天結尾我留了一題:檔案被改壞、又被改回去,現在看起來完全正常,中間那一版還查不查得到?
話不多說,老樣子,先上圖 :

今天我去查了。答案跟我原本想的相反。
查得到。 而且整條版本鏈都在,五次「改回去」一次都沒漏。
這是這個系列開始以來,第一次有東西真的成立。前四天我查一次、撞牆一次:hook 裝了不叫、兩份紀錄給出矛盾的時間、標準刻意不記我要的欄位、同一個寫檔動作分不出改的是測試還是正式碼。今天終於接上了一塊。
所以這篇的前半是好消息,後半是我接著撞上的那面牆——我手上有那份內容,卻沒辦法證明它就是我磁碟上那個檔。
我挑了一個 AI 這幾天改最多次的檔,billing.py。它在我的軌跡裡出現 15 次。
我把這 15 次寫入的內容各算一次雜湊(可以想成內容的指紋,內容一樣指紋就一樣),排成一條版本鏈:
第 4 版 291 bytes c1b92c4414ec
第 5 版 336 bytes 0c7d7e5d8bd6
第 6 版 527 bytes 96201b1bef58
第 7 版 291 bytes c1b92c4414ec ← 跟第 4 版一模一樣
...
第 9 版 336 bytes 0c7d7e5d8bd6 ← 跟第 5 版一模一樣
15 版裡有 5 次「內容完全回到先前某一版」。第 7 版退回第 4 版,第 9 版退回第 5 版,第 13 版又退回第 5 版一次。
這正是昨天問的那個情況:改壞了,改回去,現在看起來沒事。而軌跡上不但看得出「改回去」這個動作,被改回去之前那一版的完整內容也還在。
為什麼會在?因為 Cursor 的 hook 在每次寫檔之後,會把實際寫進去的完整內容交給我的腳本。所以我手上不是一句「這個檔被寫過 15 次」,是 15 份完整副本。
我以為今天要寫的缺口,其實不存在。
有副本,跟舉得出證,是兩件事。
假設有人問我:「你說第 6 版被退掉了,證據呢?」我要回答的不是檔案內容長什麼樣。我要證明的是這一句:
我手上這份紀錄,對應的就是磁碟上那個檔。
標準做法是算雜湊。兩邊指紋一樣,就認定是同一份東西。這套做法在鑑識裡叫監管鏈,用來證明證物從現場到法庭之間沒被掉包。
我算了。對不上。
磁碟 627 bytes sha256 7c77f331395d
軌跡 613 bytes sha256 23bfa51923b3
差 14 個位元組。我以為是軌跡漏收了最後一次修改,於是逐行比對,準備找出多出來的那幾行。
結果是 0 行不同。14 行,每一行都一模一樣。
14 行,差 14 個位元組。
原因是換行符。磁碟上那個檔用的是 Windows 的 CRLF(兩個位元組),軌跡收到的內容用的是 LF(一個位元組),每行差一個,14 行剛好差 14 個。我把換行統一之後再算一次,兩邊的指紋都是 23bfa51923b3,完全相同。
內容沒有任何差異,差的是寫法。這個寫法不影響程式怎麼跑,卻讓雜湊比對這個舉證手段用不了。
一個檔可能是巧合。我把軌跡裡 98 個被寫過的檔全跑了一遍:
| 裁決 | 檔數 | 佔比 |
|---|---|---|
| 逐位元組相同 | 7 | 7.1% |
| 只差換行符 | 74 | 75.5% |
| 內容真的不同 | 15 | 15.3% |
| 磁碟上已不存在 | 2 | 2.0% |
雜湊直接對得上的只有 7 個檔。 其餘 91 個裡,有 74 個內容一字不差,指紋卻是錯的。
Day 1,我裝的 hook 一筆都沒收到,而且沒有任何錯誤訊息。原因是腳本讀不掉開頭那三個看不見的位元組就當掉了,而 Cursor 的 hook 失敗會照樣放行,所以整件事無聲。
Day 2,我設陷阱測 AI 會不會作弊,順便測我的規則抓不抓得到。規則裡有一個字寫錯(content 寫成 contents),「跳過測試」那條就永遠不會響。它每次都回報正常,因為它根本沒在看。
Day 4,有一種事件其實帶了「改了哪幾行」,但我的腳本用錯欄位去讀,整筆變成空白。不是沒記,是接錯了。
今天,內容記對了,指紋對不上。
四次都有紀錄。問題是那份紀錄回答不了我要問的問題。而且四次都沒有紅燈,沒有錯誤訊息,要自己動手比對才會發現。
220 筆紀錄裡有 201 筆(91%)在傳給我的腳本途中被重新編碼過一次,中文全變成亂碼。我還原得回來,因為位元組沒有真的損失。但我知道這件事,只是因為我當初多記了一個「這筆被改過編碼」的欄位。沒有那個欄位,我會拿著一批看起來很正常的亂碼資料繼續往下查。
回到上面那張表。「內容真的不同」的 15 個檔,我目前分不出是哪一種情況。一種是軌跡漏收,AI 改了但 hook 沒攔到;另一種是那次修改根本不是 AI 做的,是我自己在編輯器裡改的,或是某個指令寫進去的。
這兩件事的嚴重程度差很多。第一種代表我的監視器有洞,第二種代表監視器沒問題,只是它本來就只看 AI。
我現在的腳本分不出來,所以我不會假裝分得出來。要分辨得把整條版本鏈重新套用一次,看斷在哪一步,那是後面重放那幾天的工作。
我最近在看 TypeSafe 九月中發表的 Jev,它不生成文字,只回校準過的機率。也許能拿來分這 15 個檔。API 我今天剛拿到,一次都還沒跑過,所以今天不會給你任何數字。而且就算跑出來了,還有一個更麻煩的問題在等著:機率算不算證據。
今天真正做出來的東西是兩個判斷式,都短到可以貼在這裡。
第一個,從寫檔紀錄找出「改回去」:把每次寫入的內容算一次雜湊,出現重複就代表內容回到了先前某一版。
import hashlib
seen = {}
for i, content in enumerate(versions, 1): # versions = 每次寫入的內容
h = hashlib.sha256(content.encode()).hexdigest()
if h in seen:
print(f"第 {i} 版 = 第 {seen[h]} 版,被改回去了")
else:
seen[h] = i
第二個,比對軌跡與磁碟時先把換行正規化,否則你會像我一樣白追一輪:
def fingerprint(data: bytes) -> str:
return hashlib.sha256(data.replace(b"\r\n", b"\n")).hexdigest()[:12]
這兩個放進你自己的專案就能跑,不需要裝任何 agent。 拿你這週改過的檔試一次:算一次原始 sha256、再算一次正規化後的,如果你手上有任何「備份」或「稽核紀錄」宣稱保存了內容,兩邊對一次。對不上的話先別認為備份壞了,先看是不是換行符。
順便打開編輯器右下角,確認它存檔用的是 LF 還是 CRLF。這一格決定你未來所有雜湊比對會不會白做。
明天我要把軌跡的欄位表定下來。這五天我查的都是它做了什麼,還沒碰到它憑什麼那樣做——而那一欄,就是我填不滿的那一欄。
對了,今天我剛好拿到 TypeSafe 的 Jev,一個不生成文字、只回機率的模型。我想知道它補得上哪幾欄、補不上哪幾欄。答案明天給你,包含它答錯的那幾題。
今天對應的威脅: T3(藉 Agent 之手規避控制)。規避之所以划算,前提是事後舉不出證。今天量到的是:在我這台機器上,這個前提成立的比例是 93%。
實驗與程式: 2026-09-19。原始輸出 research/samples/day05-chain.txt、day05-replay-billing.txt;腳本 day05_replay.py、day05_chain.py、day05_bytediff.py。軌跡來源是 Cursor postToolUse hook 落下的 hooks.jsonl,220 筆寫入與刪除紀錄、98 個檔。本篇沒有引用外部來源。